﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace AB.Common
{
    /// <summary>
    /// 
    /// </summary>
    public static class PredicateBuilder
    {

        /// <summary>
        /// 机关函数应用True时：单个AND有效，多个AND有效；单个OR无效，多个OR无效；混应时写在AND后的OR有效 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Expression<Func<T, bool>> True<T>() { return f => true; }

        /// <summary>
        /// 机关函数应用False时：单个AND无效，多个AND无效；单个OR有效，多个OR有效；混应时写在OR后面的AND有效 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Expression<Func<T, bool>> False<T>() { return f => false; }
        /// <summary>
        /// Or
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>
            (Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);
        }
        /// <summary>
        /// And
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>
            (Expression.And(expr1.Body, invokedExpr), expr1.Parameters);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, string queryString)
        {
            if (!string.IsNullOrEmpty(queryString))
            {
                string[] arry = queryString.Split(new string[] { "$" }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var item in arry)
                {
                    string[] itemarry = item.Split(new string[] { "*" }, StringSplitOptions.RemoveEmptyEntries);
                    if (itemarry.Length == 3)
                    {
                        string left = itemarry[0];
                        string right = itemarry[2];
                        string opt = itemarry[1];
                        var expression = CreateLanmbda<T>(left, right, null, opt);
                        if (expression != null)
                        {
                            expr1 = expr1.And(expression);
                        }
                    }
                }
            }
            return expr1;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyOrFieldName"></param>
        /// <param name="value"></param>
        /// <param name="ctype"></param>
        /// <param name="typeStr"></param>
        /// <param name="etype"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CreateLanmbda<T>(string propertyOrFieldName, object value, CompareType? ctype = CompareType.Equal, string typeStr = null, ExpressionType etype = ExpressionType.Equal)
        {
            try
            {
                PropertyInfo x = typeof(T).GetProperties().Where(e => e.Name == propertyOrFieldName).FirstOrDefault();
                if (x != null)
                {
                    Type type = x.PropertyType;
                    if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))//判断convertsionType是否为nullable泛型 
                    {
                        //如果type为nullable类，声明一个NullableConverter类，该类提供从Nullable类到基础基元类型的转换  
                        System.ComponentModel.NullableConverter nullableConverter = new System.ComponentModel.NullableConverter(type);
                        //将type转换为nullable对的基础基元类型  
                        type = nullableConverter.UnderlyingType;
                    }
                    //构建Lambda表达式
                    var parameter = Expression.Parameter(typeof(T));
                    //表达式左侧 like: p.Name
                    var left = Expression.Property(parameter, propertyOrFieldName);
                    //表达式右侧，比较值， like '张三'
                    var right = Expression.Constant(Convert.ChangeType(value, type), x.PropertyType);
                    Expression constant;
                    if (ctype != null || !string.IsNullOrEmpty(typeStr))
                    {
                        if (ctype == null)
                        {
                            ctype = GetCompareType(typeStr);
                        }
                        switch (ctype)
                        {
                            case CompareType.GreaterThan:
                                constant = Expression.MakeBinary(ExpressionType.GreaterThan, left, right);
                                break;
                            case CompareType.GreaterThanOrEqual:
                                constant = Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, left, right);
                                break;
                            case CompareType.LessThan:
                                constant = Expression.MakeBinary(ExpressionType.LessThan, left, right);
                                break;
                            case CompareType.LessThanOrEqual:
                                constant = Expression.MakeBinary(ExpressionType.LessThanOrEqual, left, right);
                                break;
                            case CompareType.NotEqual:
                                constant = Expression.MakeBinary(ExpressionType.NotEqual, left, right);
                                break;
                            case CompareType.Include:
                                var method = x.ReflectedType == typeof(int)
                               ? typeof(int).GetMethod("Contains", new Type[] { typeof(int) })
                               : typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
                                constant = Expression.Call(left, method, right);
                                break;
                            case CompareType.Equal:
                            default:
                                constant = Expression.MakeBinary(ExpressionType.Equal, left, right);
                                break;
                        }
                    }
                    else
                    {
                        constant = Expression.MakeBinary(etype, left, right);
                    }
                    return Expression.Lambda<Func<T, bool>>(constant, parameter);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return null;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="typestr"></param>
        /// <returns></returns>
        private static CompareType GetCompareType(string typestr)
        {
            switch (typestr)
            {
                case ">": return CompareType.GreaterThan;
                case ">=": return CompareType.GreaterThanOrEqual;
                case "<": return CompareType.LessThan;
                case "<=": return CompareType.LessThanOrEqual;
                case "<>": return CompareType.NotEqual;
                case "like": return CompareType.Include;
                case "==":
                default:
                    return CompareType.Equal;
            }
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public enum CompareType
    {
        /// <summary>
        /// 
        /// </summary>
        GreaterThan,
        /// <summary>
        /// 
        /// </summary>
        GreaterThanOrEqual,
        /// <summary>
        /// 
        /// </summary>
        LessThan,
        /// <summary>
        /// 
        /// </summary>
        LessThanOrEqual,
        /// <summary>
        /// 
        /// </summary>
        NotEqual,
        /// <summary>
        /// 
        /// </summary>
        Equal,
        /// <summary>
        /// 
        /// </summary>
        Include
    }
}
